package edu.ntu.cloudroid.server;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.jdo.PersistenceManager;

import com.google.appengine.api.log.AppLogLine;
import com.google.appengine.api.log.LogQuery;
import com.google.appengine.api.log.LogServiceFactory;
import com.google.appengine.api.log.RequestLogs;
import com.google.appengine.api.log.LogService.LogLevel;


import edu.ntu.cloudroid.model.PMF;
import edu.ntu.cloudroid.model.data.AbstractData;
import edu.ntu.cloudroid.model.data.DetachedData;
import edu.ntu.cloudroid.model.data.LocationData;
import edu.ntu.cloudroid.model.data.VitalSignData;
import edu.ntu.cloudroid.model.friend.FriendData;
import edu.ntu.cloudroid.model.friend.FriendInfo;
import edu.ntu.cloudroid.model.friend.GroupInfo;
import edu.ntu.cloudroid.model.policy.AbstractPrivacyPreference;
import edu.ntu.cloudroid.model.policy.PolicyInfo;
import edu.ntu.cloudroid.model.policy.SimpleFiltering;
import edu.ntu.cloudroid.model.profile.ProfileData;
import edu.ntu.cloudroid.model.profile.ProfileInfo;
import edu.ntu.cloudroid.nongui.com.LogReply;
import edu.ntu.cloudroid.nongui.com.LogRequest;
import edu.ntu.cloudroid.nongui.com.VitalRequestWithFilteringFunction;
import edu.ntu.cloudroid.nongui.com.RequestNoInputs;
import edu.ntu.cloudroid.nongui.com.AbstractResponse;
import edu.ntu.cloudroid.nongui.util.TimeCounter;
import edu.ntu.cloudroid.server.backendservice.DataService;
import edu.ntu.cloudroid.server.backendservice.FriendService;
import edu.ntu.cloudroid.server.backendservice.GroupService;
import edu.ntu.cloudroid.server.backendservice.PolicyService;
import edu.ntu.cloudroid.server.backendservice.ProfileService;
import edu.ntu.cloudroid.xacml.ClouDroidPEP;

/**
 * @author ttadinh Provide an interface to access all service at the back end.
 * 
 *         A query from a client must pass the PEP evaluation first. Then, the
 *         data is access using the services and returning the data.
 */
public class BackendServiceManager {
	private static ClouDroidPEP pep = new ClouDroidPEP();
	static TimeCounter counter = new TimeCounter("data request", RequestServlet.logger);
	static LogQuery query = LogQuery.Builder.withDefaults();
	public static AbstractResponse getLogOffset(LogRequest lr){
		//if log request contains null offset, get the current one				
		query.includeAppLogs(true);
		query.minLogLevel(LogLevel.INFO);
		String offset="";
		if (lr.getCurrentOffset().equals("null")){					
			for (RequestLogs record : LogServiceFactory.getLogService().fetch(query)) {
				offset = record.getOffset();
				break;
			}			
			RequestServlet.logger.info("return requesting log offset");
			return new LogReply(offset);
		}
		else{
			ArrayList<String> logMsgs = new ArrayList<String>();
			String latestOffset=null;
			long start = System.currentTimeMillis();
			for (RequestLogs record : LogServiceFactory.getLogService().fetch(query)) {
				if (latestOffset==null)
					latestOffset = record.getOffset();
				if (record.getOffset().equals(lr.getCurrentOffset()))
					break;				
				logMsgs.add("instance: "+record.getInstanceKey()+"\n");												
				for (AppLogLine appLog : record.getAppLogLines()) {
					logMsgs.add(appLog.getLogMessage()+"\n");					
				}								
				if ((System.currentTimeMillis()-start)>=56000)
					break;
			}
			RequestServlet.logger.info("number of log msgs = "+logMsgs.size());
			return new LogReply(logMsgs, latestOffset);
		}		
	}
	
	public static ProfileInfo getProfileMetaData(String userEmail)
			throws Exception {
		return (ProfileInfo) ProfileService.getProfile(userEmail).getMetaData(
				null);
	}

	public static FriendInfo getFriendMetaData(String userEmail)
			throws Exception {
		
		PersistenceManager pm = PMF.get().getPersistenceManager();
		try{
		List<FriendData> fd = FriendService.getFriendList(userEmail, pm);
		List<String> friends = new ArrayList<String>();
		for (FriendData friend: fd){
			friends.add(friend.getFriendName());
		}
		return new FriendInfo(userEmail, friends, FriendInfo.REPLY);
		}
		finally{
			pm.close();
		}
	}

	public static PolicyInfo getDefaultPolicyMetaData(String userEmail)
			throws Exception {
		ProfileData p = ProfileService.getProfile(userEmail);
		return (PolicyInfo) PolicyService.getDefaultPolicy(p).getMetaData(null);
	}

	public static GroupInfo getGroupMetaData(String userEmail, String groupName)
			throws Exception{
		return (GroupInfo) GroupService.findGroupWithName(groupName)
				.getMetaData(null);
	}

	public static AbstractResponse getDataForDirectRequest(
			VitalRequestWithFilteringFunction request) throws Exception{
		/*long start = */counter.start(false);
		//first, get the policy
		PolicyInfo policy = request.getPolicy();
		String[] row = policy.getPreferenceString();		
		int privacyType = new Integer(row[1]).intValue();		
		
		/*SimpleFiltering filteringPolicy = (SimpleFiltering) PolicyService
				.getPolicyObject(privacyType);
		filteringPolicy.initialize(row);		
		String val = filteringPolicy.getConstraintValue()+"";
		int colIdx = filteringPolicy.getDataColumnIndex();*/
		
		ProfileData subject = ProfileService.getProfile(request
				.getRequesterID());
		//ProfileData resource;
		List<ProfileData> friends = FriendService.getFriendsProfile(request.getRequesterID());
		
		VitalSignData ld;
		Map<String, Map<String, List<DetachedData>>> response = new HashMap<String, Map<String, List<DetachedData>>>();
		for (ProfileData friend: friends){
			Date ts = FriendService.getFriendTimeStamp(friend.getEmailAddress(), subject.getEmailAddress());
			List<AbstractData> data = DataService.getData(friend, ts,
					friend.getDataId());
			
			SimpleFiltering filteringPolicy = (SimpleFiltering) PolicyService.getDefaultPolicy(friend);
			String val = filteringPolicy.getConstraintValue()+"";
			int colIdx = filteringPolicy.getDataColumnIndex();
			List<DetachedData> returnedData = new ArrayList<DetachedData>();
			Date newTs = null;
			for (AbstractData ad : data){
				ld = (VitalSignData)ad;
				if (filteringPolicy.getConditionType() == SimpleFiltering.EQUAL && ld.compareTo(val, colIdx) == 0)
					returnedData.add(ld.detach(colIdx));
				if (filteringPolicy.getConditionType() == SimpleFiltering.LEQUAL && ld.compareTo(val, colIdx) == -1)
					returnedData.add(ld.detach(colIdx));
				if (filteringPolicy.getConditionType() == SimpleFiltering.GEQUAL && ld.compareTo(val, colIdx) == 1)
					returnedData.add(ld.detach(colIdx));
				newTs = ld.getTimestamp();
			}
			//then update the timestamp
			if (returnedData.size() > 0) {
				FriendService.delayTimestampUpdate(friend.getEmailAddress(),subject.getEmailAddress(), newTs);
				FriendService.updateFriendTimeStamp(friend.getEmailAddress(), subject.getEmailAddress());
				Map<String, List<DetachedData>> temp = new HashMap<String, List<DetachedData>>();
				temp.put("filtering", returnedData);
				response.put(friend.getEmailAddress(), temp);
			}
		}
		/*System.out.printf("#!@ %d\n", (*/counter.end(false) /*- start))*/;
		
		return new AbstractResponse(subject.getEmailAddress(), true, response);		
	}
	
	public static AbstractResponse getDataFromAllFriends(RequestNoInputs request)
			throws Exception {
		/*long begin = */counter.start(true);
		ProfileData subject = ProfileService.getProfile(request
				.getRequesterID());
		//ProfileData resource;
		List<ProfileData> friends = FriendService.getFriendsProfile(subject.getEmailAddress());
		Map<String, Map<String, List<DetachedData>>> data = new HashMap<String, Map<String, List<DetachedData>>>();

		//for (ProfileData friend : friends) {
			if (friends.size()>0){
			ProfileData friend = friends.get((new Random()).nextInt(friends.size()));	
			
			Map<String, List<DetachedData>> returnedData = pep.evaluateRequest(
					subject, friend, request);
			if (returnedData != null && returnedData.size() > 0) {
				data.put(friend.getEmailAddress(), returnedData);
			}
			}
		//}
		
		/*System.out.printf("#@# %d\n", (*/counter.end(true) /*- begin))*/;
		if (data.size() > 0)
			return new AbstractResponse(subject.getEmailAddress(), true, data);
		else
			return new AbstractResponse(subject.getEmailAddress(), true, null);
	}
	
	public static AbstractResponse getDataFromAFriend(RequestNoInputs request) throws Exception {
		counter.start(true);
		long startTime = System.currentTimeMillis();
		ProfileData subject = ProfileService.getProfile(request
				.getRequesterID());
		ProfileData resource = ProfileService.getProfile(request
				.getRequestedID());		
		RequestServlet.logger.info("Checking relationship "+resource.getEmailAddress()+", "+subject.getEmailAddress());
		FriendData check = FriendService.getFriendRelation(resource.getEmailAddress(), subject.getEmailAddress());		
		if (check==null){
			RequestServlet.logger.info("not friend");
			counter.end(true);
			long et = System.currentTimeMillis();
			System.out.println("overall request = "+(et-startTime));
			return new AbstractResponse(subject.getEmailAddress(), false, null);
		}
		//List<ProfileData> friends = FriendService.getFriendsProfile(subject
		//		.getFriendListKey());
		Map<String, Map<String, List<DetachedData>>> data = new HashMap<String, Map<String, List<DetachedData>>>();
		startTime = System.currentTimeMillis();
		Map<String, List<DetachedData>> returnedData = pep.evaluateRequest(
				subject, resource, request);
		long endTime = System.currentTimeMillis();
		System.out.println("overall request = "+(endTime-startTime));
		if (returnedData != null) {
			data.put(resource.getEmailAddress(), returnedData);
		}
		counter.end(true);
		if (data.size() > 0)
			return new AbstractResponse(subject.getEmailAddress(), true, data);
		else
			return new AbstractResponse(subject.getEmailAddress(), true, null);
	}
}